A first window

Making interfaces can be a challenging task, but nkWinUi tries its best to make it easy for you. In this first tutorial, we will see how the component can be initialized, how UI elements can be created, and how the basic event handling system can be set and used.

With this, all the basics of the component usage will be covered !

Note that images presented in this tutorial were made on Windows. The look and feel can vary from a system to another.

Initialization

First step to use nkWinUi is to initialize the component. Like other components using this pattern within the Nilkins ecosystem, this allows it to allocate everything it needs and setup what depends on the system requirements.

To do so, first include the MainSystem header :

#include <NilkinsWinUi/System.h>

Now, we can call its initialization method to prepare it :

nkWinUi::System* uiSystem = nkWinUi::System::getInstance() ; if (!uiSystem->initialize()) return -1 ;

After that call, if successful, our system is ready to work. It allocated all its basic resources, detected the system it is running on and adapted its behaviour in consequence.

Creating UI components

From there, we can start creating what is called a Component in nkWinUi. This corresponds to a UI element. Components can be a window, a slider, an edit box... All available interface bits are enumerated within the COMPONENT_TYPE enum class.

Let's create a basic window, supposed to contain all other components we will have.

#include <NilkinsWinUi/Components/ComponentManager.h> #include <NilkinsWinUi/Components/Window.h>

With these includes we can now get the ComponentManager's instance and use the Window component :

nkWinUi::ComponentManager* componentManager = nkWinUi::ComponentManager::getInstance() ;

The ComponentManager is a singleton managing all components, aka UI elements, that have been created. It is responsible for allocating the right resources for the system, and owns all memory returned. If you already manipulated other components, within the framework, exposing resources, this pattern will probably feel familiar. Now is the time to use it :

nkWinUi::Window* mainWindow = (nkWinUi::Window*)componentManager->createOrRetrieve("mainWindow", nkWinUi::COMPONENT_TYPE::WINDOW) ;

We create or retrieve, meaning that if non-existing, component will be created, but if available it will only be returned. This component will be identified by the mainWindow name, so that we can later refer to it within the manager. Lastly, it will be of type WINDOW.

The manager creates each component available in nkWinUi. As such, this function returns the base class, a Component. However, specific classes of components can expose specific API. As such, it is common to cast them to their specialized type when retrieving them. To know which enumeration entry identifies which class, be sure to check the COMPONENT_TYPE's API documentation. Here, the WINDOW entry returns a pointer over a component which is in fact a Window. This is why we cast it right away.

Now that we have a component to work with, let's see for its basic setup :

mainWindow->setWidth(800) ; mainWindow->setHeight(600) ; mainWindow->load() ;

We set its pixel width and height, and load it. Loading is an important part within the resource management in nkWinUi, like in any other component of the framework. It will trigger the underlying creation mechanism and use all the parameters previously setup to finalize the resource wanted.

UI Loop

We could launch the program now and see a nice window popping out. However if you are starting from scratch like in this tutorial, you will end up with the window flashing as the program shuts down right away.

What we need is a loop in which we will keep the program alive. Let's put its base together :

bool windowOpened = true ;

This little boolean flag will be our loop continuation flag. Let's see how it is used in the loop :

while (windowOpened) uiSystem->tick() ;

This will be our simple update loop : while the flag is on, do something.

This something here is the ticking of the nkWinUi system. When incorporating nkWinUi into an existing project, it is important to have this ticking in your own loop in the UI thread. It updates and processes all events and inputs. As such, without this, you will get an unresponsive UI, as events won't be taken into account.

Now that we have our loop, we could launch the program, but beware that the flag will be on forever, meaning you will need to terminate the process manually... Either by stopping the debug session, or closing the console forcefully.

We need something to alter this flag during runtime... And as it turns out, what we just enabled with this ticking method is the processing of events within the UI. So why not use that ?

Setting up global callbacks

Callbacks are separated into 2 categories : global and local. Local callbacks are specified on a component itself, and will only be called by that component alone. They are local to the component they are declared on. Global callbacks are specified once in the InputSystem and will be called for any component with no corresponding local callback. They're global to all components. Note that the same local callback function can be specified over more than one component.

Here, we will first introduce the global callbacks. Namely, we will setup the closing callback which will be able to alter the flag we have in our loop.

As a result, let's include what we need :

#include <NilkinsWinUi/Inputs/InputSystem.h>

The InputSystem is the part that will receive our global closing callback. The code introduced next has to be added between the flag declaration and the loop start :

nkWinUi::InputSystem::getInstance()->setCloseCallback ( [&windowOpened] (nkWinUi::Component* caller) -> bool { windowOpened = false ; // We handled the event, return true to mean that the component itself can continue and proceed with its unloading return true ; } ) ;

InputSystem is a singleton that can hold all information about global callbacks. It is like a repository of callbacks specified that all components with no local callback will be able to address. It also presents some other functionalities, but they won't be covered in this tutorial.

A component's basic callback signature is defined by the fact that it takes the calling component in parameter, and returns a boolean indicating if the event should still be propagated. Here, we use it in a simple way :

  1. Capture the flag through a reference
  2. Turn it to false on call
  3. Still return false, we need the window to also process the event and close its UI part

Launching the program now, and closing it through a click on the cross will now close the window and end the program as intended. You can give it a try !

Proper shutdown

We now have a program with nkWinUi, working from A to around X or Y. To have it working up to Z, we need to gracefully shut it down. This operation is handled upon the singleton destruction, but it is good practice to clean up everything manually.

To do so, after the loop is exited and before the program ends, we will need to add :

uiSystem->prepareForShutdown() ; uiSystem->kill() ;

The shutdown preparation method will clean all memory and free everything that has been allocated during initialization. We then kill the singleton to ensure it is cleaned up.

And with that, we now have a base to work with nkWinUi ! Behold our mighty window !

Basic window
Mighty window in action

Component-ception

Our window is living which is a good thing, but it would be better if we could have something inside to interact with. Why not adding a button inside ? We will be able to inject some logic in there to make it spicier.

Gathering the knowledge we now have from preceding parts, we can instanciate a Button, by first including :

#include <NilkinsWinUi/Components/Button.h>

Then, using the ComponentManager, we get :

nkWinUi::Button* clickButton = (nkWinUi::Button*)componentManager->createOrRetrieve("clickButton", nkWinUi::COMPONENT_TYPE::BUTTON) ; clickButton->setLabel("Click me !") ; clickButton->setAreaInParent(nkMaths::Rectangle(350, 275, 100, 100)) ; clickButton->setParentComponent(mainWindow) ; clickButton->load() ;

This time, we create a BUTTON, that we cast back to its specialized class, Button. Then, we setup some info on it : its label, meaning the text displayed, and its area, the position and size it will take, in pixels.

Another important bit is to set its parent component. This will make the button be part of the window we just created. Else, it would just be floating around. Also, it will change its local reference, and all coordinates expressed are now relative to the parent component itself. This is important to keep in mind when working with different layers of components.

Final step is to load the button, of course, to trigger all its internal initialization.

Once this is done, we can open the program again and witness something new :

Button in the window
A button popped !

However nice this may be, clicking it will hold no result... Yet. We need to specify a callback for it when it is clicked. This time, we will use a local callback. Let's include :

#include <iostream>

And use it in a callback :

clickButton->setButtonClickCallback ( [] (nkWinUi::Component* caller) -> void { std::cout << "Button clicked !" << std::endl ; } ) ;

This time, we directly set the callback on the button itself. This is how we can create local callbacks, by associating them to a specific component.

The callback signature is the same as the last one we had. However, it is currently associated with only one component : our button. As such, the caller will always be the same. Note that this parameter can still be important as the same callback function can be given to many components, like mentioned earlier. In such case, the caller will be amongst the set of components the function is linked to.

Back to our function, it simply logs that the button has been clicked, before returning true. This time, there is no need to transmit the event to anything else in the chain, we signify that by returning true. If we launch the program again and click the button :

Button clicked !

How is that for a spicier UI ?

Component logging

Like all other components within the Nilkins framework, nkWinUi logs errors and warnings through the LogManager. To be able to get those messages, let's include :

#include <NilkinsLog/Loggers/ConsoleLogger.h> ... #include <NilkinsWinUi/Log/LogManager.h> ... #include <memory>

Then, we can use all of that to prepare the LogManager to log to the console :

std::unique_ptr<nkLog::Logger> logger = std::make_unique<nkLog::ConsoleLogger>() ; nkWinUi::LogManager::getInstance()->setReceiver(logger.get()) ;

With that, each message logged will go through the ConsoleLogger we just allocated. This will reroute all messages to the console, if available.

Conclusion

And with that, we have now seen all the basics in nkWinUi. We can now initialize and shutdown the component, create and manipulate UI elements, and listen to events / inputs through the callbacks.

Next to come are more tools to help us build our UI into something more fancy. Once ready, let's dig next tutorial !